/* Necessary includes for device drivers */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h> /* printk() */
#include <linux/sched.h>
#include <linux/slab.h> /* kmalloc() */
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h> /* everything... */
#include <linux/types.h> /* size_t */
#include <asm/uaccess.h> /* copy_from/to_user */

#define MEMORY_SIZE 10
MODULE_LICENSE("Dual BSD/GPL");

/* Declaration of memory.c functions */
int memory_open(struct inode *inode, struct file *filp);
int memory_release(struct inode *inode, struct file *filp);
ssize_t memory_read(struct file *filp, char *buf, size_t count, loff_t *f_pos);
ssize_t memory_write(struct file *filp, char *buf, size_t count, loff_t *f_pos);
void memory_exit(void);
int memory_init(void);

DECLARE_WAIT_QUEUE_HEAD(data_ready);

/* Structure that declares the usual file */
/* access functions */
struct file_operations memory_fops = {
  read: memory_read,
  write: memory_write,
  open: memory_open,
  release: memory_release
};


/* Global variables of the driver */
/* Major number */
int memory_major;
/* Buffer to store data */
char *memory_buffer;
int buffer_size;
struct class *memory_class;

int memory_init(void) {
  struct device *err_dev;

  /* Registering device */
  memory_major = register_chrdev(0, "memory", &memory_fops);
  if (memory_major < 0) {
    printk(
      "<1>memory: cannot obtain major number %d\n", memory_major);
    return memory_major;
  }

  /* Allocating memory for the buffer */
  memory_buffer = kmalloc(MEMORY_SIZE, GFP_KERNEL); 
  if (!memory_buffer) { 
    memory_major = -ENOMEM;
    goto fail; 
  } 
  memset(memory_buffer, 0, 1);

  memory_class = class_create(THIS_MODULE,"memory");
  err_dev = device_create(memory_class, NULL, MKDEV(memory_major,0),NULL,"memory");
  printk("<1>Inserting memory module, Major: %d\n",memory_major); 
  return 0;

  fail: 
    memory_exit(); 
    return memory_major;
}

void memory_exit(void) {
  /* Freeing the major number */
  unregister_chrdev(memory_major, "memory");

  /* Freeing buffer memory */
  if (memory_buffer) {
    kfree(memory_buffer);
  }

  device_destroy(memory_class,MKDEV(memory_major,0));
  class_unregister(memory_class);
  class_destroy(memory_class);
  printk("<1>Removing memory module\n");

}

int memory_open(struct inode *inode, struct file *filp) {

  /* Success */
  return 0;
}

int memory_release(struct inode *inode, struct file *filp) {
 
  /* Success */
  return 0;
}

ssize_t memory_read(struct file *filp, char *buf, 
                    size_t count, loff_t *f_pos) { 

  size_t c;
  wait_event_interruptible(data_ready,(buffer_size>0));
  c = count>buffer_size ? buffer_size: count;
  if (*f_pos>0) return 0;
  /* Transfering data to user space */ 
  copy_to_user(buf,memory_buffer,c);

  printk("Memory read: %d \n",c);
  /* Changing reading position as best suits */ 
  if (c >= 0) { 
    *f_pos+=c; 
    return c; 
  } else { 
    return 0;
  }
}
ssize_t memory_write( struct file *filp, char *buf,
                      size_t count, loff_t *f_pos) {
  size_t c;
  c = count>MEMORY_SIZE ? MEMORY_SIZE: count;
  printk("<1>Memory wrire %d\n",c); 
  copy_from_user(memory_buffer,buf,c);
  buffer_size=c;
  wake_up_interruptible(&data_ready);
  return count;
}

/* Declaration of the init and exit functions */
module_init(memory_init);
module_exit(memory_exit);
